package com.clp.protocol.iec104.client;

import com.clp.protocol.core.client.ClientCloseEvent;
import com.clp.protocol.core.client.ClientEventListenerRegistrar;
import com.clp.protocol.core.client.ClientEventPublisher;
import com.clp.protocol.core.common.connect.NettyClient;
import com.clp.protocol.core.exception.FailedToConnectException;
import com.clp.protocol.iec104.client.async.*;
import com.clp.protocol.iec104.client.event.ClientConnectEvent;
import com.clp.protocol.iec104.client.master_config.MasterConnConfig;
import com.clp.protocol.iec104.client.pipeline.Iec104ChannelInitializer;
import com.clp.protocol.iec104.client.ui.MonitorPanel;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
 * 单例的104客户端，既是主站的容器，也是管理主站入口
 */
@Slf4j
public class Iec104Client implements Closeable {
    private static volatile Iec104Client iec104Client;

    private final NettyClient<Iec104ChannelInitializer> nettyClient = new NettyClient<>(); // 内嵌netty客户端
    private final MasterContainer container = new MasterContainer(); // 主站容器
    private final ClientEventPublisher<Iec104Client> eventPublisher; // 事件发布器
    private volatile MonitorPanel monitorPanel = null;

    private Iec104Client() {
        this.eventPublisher = new ClientEventPublisher<>(this, nettyClient.scheduledExecutor());
    }

    public static Iec104Client get() {
        // 懒加载
        if (iec104Client != null && !iec104Client.isClosed()) {
            return iec104Client;
        }
        synchronized (Iec104Client.class) {
            if (iec104Client != null && !iec104Client.isClosed()) {
                return iec104Client;
            }
            iec104Client = new Iec104Client();
        }
        return iec104Client;
    }

    @Nullable
    public Master getMaster(String remoteHost, int remotePort) {
        return container.getOne(remoteHost, remotePort);
    }

    public boolean hasMaster(String remoteHost, int remotePort) {
        return container.contains(remoteHost, remotePort);
    }

    public void forEachMaster(Consumer<Master> consumer) {
        container.forEach(consumer);
    }

    public ClientEventListenerRegistrar<Iec104Client> getEventListenerRegistrar() {
        return eventPublisher;
    }

    /**
     * 获取 Panel 监视面板
     *
     * @return
     */
    public MonitorPanel getMonitorPanel() {
        if (monitorPanel != null) return monitorPanel;
        synchronized (this) {
            if (monitorPanel != null) return monitorPanel;
            monitorPanel = new MonitorPanel(this);
        }
        return monitorPanel;
    }

    /**
     * 根据指定的配置进行连接
     *
     * @param cfg 主站连接配置
     * @return 对应的主站对象
     */
    public synchronized MasterFuture<Void> connect(MasterConnConfig cfg) {
        checkNotClosed();

        String remoteHost = cfg.getRemoteHost();
        int remotePort = cfg.getRemotePort();
        try {
            // 连接前
            publishConnectEvent(ClientConnectEvent.Type.NEW_CONNECT_BEFORE, remoteHost, remotePort);

            // 如果检查配置失败，则连接失败
            cfg.check();

            MasterFuture<Void> startFuture = new MasterImpl(nettyClient, cfg).connect();
            startFuture.addListener(new MasterFutureListener<Void>() {
                @Override
                public void operationComplete(MasterFuture<Void> future) {
                    if (future.isSuccess()) {
                        container.add(startFuture.master());
                        publishConnectEvent(ClientConnectEvent.Type.NEW_CONNECT_SUCCESS, remoteHost, remotePort);
                    } else {
                        publishConnectEvent(future.cause(), ClientConnectEvent.Type.NEW_CONNECT_FAILED, remoteHost, remotePort);
                    }
                }
            });
            return startFuture;
        } catch (Throwable t) {
            FailedMasterPromise failedMasterPromise = new FailedMasterPromise(null, t.getCause());
            publishConnectEvent(t.getCause(), ClientConnectEvent.Type.NEW_CONNECT_FAILED, remoteHost, remotePort);
            return failedMasterPromise;
        }
    }

    /**
     * 发布连接事件
     * @param cause 如果是失败事件，说明原因
     * @param type 连接事件的类型
     * @param remoteHost 远程ip
     * @param remotePort 远程端口号
     */
    private void publishConnectEvent(@Nullable Throwable cause, ClientConnectEvent.Type type, String remoteHost, int remotePort) {
        eventPublisher.publishEvent(new ClientConnectEvent(this, cause, type, remoteHost, remotePort));
    }

    private void publishConnectEvent(ClientConnectEvent.Type type, String remoteHost, int remotePort) {
        publishConnectEvent(null, type, remoteHost, remotePort);
    }

    /**
     * 重连
     *
     * @param remoteHost 远程ip
     * @param remotePort 远程端口号
     * @return 异步结果
     */
    public synchronized MasterFuture<Void> reconnect(String remoteHost, int remotePort) {
        return reconnect(container.getOne(remoteHost, remotePort));
    }

    /**
     * 主动重连某个主站
     *
     * @param master
     * @return
     */
    public synchronized MasterFuture<Void> reconnect(Master master) {
        checkNotClosed();

        if (master == null) {
            log.error("重连失败，该主站不存在！");
            return new FailedMasterPromise(null, new FailedToConnectException("主站不存在"));
        }
        // 从集合中移除
        container.remove(master);
        MasterFuture<Void> reconnectFuture = ((MasterImpl) master).reconnect();
        reconnectFuture.addListener(new MasterFutureListener<Void>() {
            @Override
            public void operationComplete(MasterFuture<Void> future) {
                if (future.isSuccess()) {
                    Master reMaster = reconnectFuture.master();
                    container.add(reMaster);
                    publishConnectEvent(ClientConnectEvent.Type.RE_CONNECT_SUCCESS, reMaster.remoteHost(), reMaster.remotePort());
                } else {
                    publishConnectEvent(future.cause(), ClientConnectEvent.Type.RE_CONNECT_SUCCESS, master.remoteHost(), master.remotePort());
                }
            }
        });
        return reconnectFuture;
    }

    /**
     * 关闭所有主站连接
     *
     * @return 异步结果
     */
    public synchronized ClientFuture<Void> disconnectAll() {
        checkNotClosed();

        DefaultClientPromise<Void> promise = new DefaultClientPromise<>(this);

        if (isClosed() || container.isEmpty()) {
            return promise.setSuccess();
        }

        List<MasterFuture<Void>> futures = new ArrayList<>();
        for (Master master : container) {
            futures.add(disconnect(master));
        }

        final AtomicInteger count = new AtomicInteger(0);
        futures.forEach(future -> {
            future.addListener(new MasterFutureListener<Void>() {
                @Override
                public void operationComplete(MasterFuture<Void> future) {
                    if (future.isSuccess()) {
                        if (count.incrementAndGet() == futures.size()) {
                            promise.setSuccess();
                        }
                    }
                }
            });
        });
        return promise;
    }

    /**
     * 关闭指定的主站，如果自动重连失败也会调用这个方法进行关闭（虽然已经关闭了）
     *
     * @param master        要关闭的主站
     * @return
     */
    public synchronized MasterFuture<Void> disconnect(@Nullable Master master) {
        checkNotClosed();

        if (master == null) {
            return new FailedMasterPromise(master, new RuntimeException("主站不存在！"));
        }

        publishConnectEvent(ClientConnectEvent.Type.DIS_CONNECT_BEFORE, master.remoteHost(), master.remotePort());

        if (isClosed()) {
            FailedMasterPromise failedMasterPromise = new FailedMasterPromise(master, new RuntimeException("客户端已关闭！"));
            publishConnectEvent(failedMasterPromise.cause(), ClientConnectEvent.Type.DIS_CONNECT_FAILED, master.remoteHost(), master.remotePort());
            return failedMasterPromise;
        }

        container.remove(master);
        MasterFuture<Void> disconnFuture = ((MasterImpl) master).disconnect();
        disconnFuture.addListener(future -> {
            if (future.isSuccess()) {
                publishConnectEvent(ClientConnectEvent.Type.DIS_CONNECT_SUCCESS, master.remoteHost(), master.remotePort());
            } else {
                publishConnectEvent(future.cause(), ClientConnectEvent.Type.DIS_CONNECT_FAILED, master.remoteHost(), master.remotePort());
            }
        });
        return disconnFuture;
    }

    /**
     * 客户端是否关闭
     *
     * @return
     */
    private boolean isClosed() {
        return nettyClient.isClosed();
    }

    private void checkNotClosed() {
        if (isClosed()) {
            throw new RuntimeException("客户端已关闭！");
        }
    }

    @Override
    public void close() throws IOException {
        try {
            if (isClosed()) return;
            disconnectAll().sync();
            publishCloseEvent(ClientCloseEvent.Type.CLOSE_BEFORE);
            nettyClient.close();
        } catch (Exception ex) {
            publishCloseEvent(ex, ClientCloseEvent.Type.CLOSE_FAILED);
            throw ex;
        }
        publishCloseEvent(ClientCloseEvent.Type.CLOSE_SUCCESS);
    }

    private void publishCloseEvent(@Nullable Throwable cause, ClientCloseEvent.Type type) {
        eventPublisher.publishEvent(new ClientCloseEvent<>(this, cause, type));
    }

    private void publishCloseEvent(ClientCloseEvent.Type type) {
        publishCloseEvent(null, type);
    }
}
